package com.leyou.search.service.impl;

import com.leyou.common.dto.PageDTO;
import com.leyou.common.exceptions.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.SearchParamDTO;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.dto.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

import static com.leyou.search.constants.SearchConstants.*;

/**
 * @author 虎哥
 */
@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository repository;
    @Autowired
    private ItemClient itemClient;

    @Override
    public void createIndexAndMapping() {
        // 先删除索引库
        try {
            repository.deleteIndex();
        } catch (Exception e) {
            log.info("索引库不存在，需要重新创建！");
        }
        // 创建索引库
        repository.createIndex(INDEX_SOURCE);
        log.info("索引库创建完毕！");
    }

    @Override
    public void loadData() {
        log.info("准备加载商品数据");
        int page = 1, rows = 100;
        while (true){
            log.info("开始查询第{}页商品数据", page);
            // 查询出spu
            PageDTO<SpuDTO> pageInfo = itemClient.querySpuByPage(page, rows, true, null, null, null);
            List<SpuDTO> spuList = pageInfo.getItems();

            // 准备Goods的集合,把spu数据转为goods
            List<Goods> goodsList = spuList.stream().map(this::buildGoods).collect(Collectors.toList());

            // 写入索引库
            repository.saveAll(goodsList);
            log.info("第{}页数据已经成功导入索引库", page);

            // 翻页
            page++;
            // 已经是最后一页了，结束
            if(page > pageInfo.getTotalPage()){
                log.info("所有数据导入完成，共{}页， {}条数据", page-1, pageInfo.getTotal());
                break;
            }
        }
    }

    @Override
    public Mono<List<String>> getSuggestion(String key) {
        return repository.suggestBySingleField(SUGGESTION_FIELD, key);
    }

    @Override
    public Mono<PageInfo<Goods>> searchGoods(SearchParamDTO paramDTO) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 1.关键字搜索
        MatchQueryBuilder basicQuery = buildBasicQuery(paramDTO);
        sourceBuilder.query(basicQuery);

        // 2.排序
        String sortBy = paramDTO.getSortBy();
        Boolean desc = paramDTO.getDesc();
        if (StringUtils.isNotBlank(sortBy)) {
            // 排序字段非空，需要排序
            sourceBuilder.sort(sortBy, desc ? SortOrder.DESC : SortOrder.ASC);
        }

        // 3.分页
        int size = paramDTO.getSize();
        sourceBuilder.from(paramDTO.getFrom()).size(size);

        // 4.高亮
        sourceBuilder.highlighter(
                new HighlightBuilder().field(SEARCH_FIELD).preTags("<am>").postTags("</am>"));

        // 5.source过滤，控制返回值字段数量
        sourceBuilder.fetchSource(new String[]{"id", "title", "prices", "image", "sold"}, null);

        // 5.搜索
        return repository.queryBySourceBuilderForPageHighlight(sourceBuilder);
    }

    @Autowired
    private RestHighLevelClient client;

    @Override
    public Mono<Map<String, List<?>>> getFilterList(SearchParamDTO paramDTO) {
        // 1.条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 1.1.query
        MatchQueryBuilder basicQuery = buildBasicQuery(paramDTO);
        sourceBuilder.query(basicQuery).size(0);
        // 1.2.聚合分类
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryAgg").field("categoryId").size(20));
        // 1.3.聚合品牌
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(20));
        // 1.4.聚合规格参数
        sourceBuilder.aggregation(
                AggregationBuilders.nested("specAgg", "specs")
                .subAggregation(AggregationBuilders.terms("specNameAgg").field("specs.name").size(20)
                    .subAggregation(AggregationBuilders.terms("specValueAgg").field("specs.value").size(20))
                )
        );

        // 2.请求对象
        SearchRequest request = new SearchRequest();
        // 3.准备请求参数
        request.source(sourceBuilder);
        // 4.发出请求
        return Mono.create(monoSink -> {
            client.searchAsync(request, RequestOptions.DEFAULT, new ActionListener<SearchResponse>() {
                @Override
                public void onResponse(SearchResponse response) {
                    // 5.解析响应结果
                    Map<String, List<?>> filterList = handleAggregationResponse(response);
                    // 发射结果
                    monoSink.success(filterList);
                }
                @Override
                public void onFailure(Exception e) {
                    // 发射失败
                    monoSink.error(e);
                }
            });
        });
    }

    @Override
    public void saveSpuById(Long spuId) {
        // 根据id查询spu
        SpuDTO spu = itemClient.queryGoodsById(spuId);
        // 构建goods
        Goods goods = buildGoods(spu);
        // 新增
        repository.save(goods);
    }

    @Override
    public void deleteById(Long spuId) {
        repository.deleteById(spuId);
    }

    private Map<String, List<?>> handleAggregationResponse(SearchResponse response) {
        // 5.解析结果
        Map<String, List<?>> filterList = new LinkedHashMap<>();
        Aggregations aggregations = response.getAggregations();
        // 5.1.解析分类的结果
        // 根据名称获取聚合结果
        Terms categoryAgg = aggregations.get("categoryAgg");
        if (categoryAgg == null) {
            return filterList;
        }
        // 获取buckets
        List<? extends Terms.Bucket> buckets = categoryAgg.getBuckets();
        // 获取buckets中的key，也就是分类的id
        List<Long> categoryIds = buckets.stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据分类id查询分类集合
        List<CategoryDTO> categories = itemClient.queryCategoryByIds(categoryIds);
        // 存入map
        filterList.put("分类", categories);
        // 5.2.解析分类的结果
        // 根据名称获取聚合结果
        Terms brandAgg = aggregations.get("brandAgg");
        if (brandAgg == null) {
            return filterList;
        }
        // 获取buckets
        List<? extends Terms.Bucket> brandBuckets = brandAgg.getBuckets();
        // 获取buckets中的key，也就是分类的id
        List<Long> brandIds = brandBuckets.stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据分类id查询分类集合
        List<BrandDTO> brands = itemClient.queryBrandByIds(brandIds);
        // 存入map
        filterList.put("品牌", brands);
        // 5.3.解析规格参数
        // 根据名称获取聚合结果
        Nested specAgg = aggregations.get("specAgg");
        // 获取名称聚合
        Terms specNameAgg = specAgg.getAggregations().get("specNameAgg");
        if (specNameAgg == null) {
            return filterList;
        }
        // 获取buckets
        for (Terms.Bucket nameBucket : specNameAgg.getBuckets()) {
            // 获取桶中的key，其实就是规格参数的名称
            String specName = nameBucket.getKeyAsString();
            // 获取该name下面的value的聚合结果
            Terms specValueAgg = nameBucket.getAggregations().get("specValueAgg");
            // 获取value的buckets，并且将其中的key转为字符串集合
            List<String> valueList = specValueAgg.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .collect(Collectors.toList());
            // 存入map
            filterList.put(specName, valueList);
        }
        return filterList;
    }

    private MatchQueryBuilder buildBasicQuery(SearchParamDTO paramDTO) {
        String key = paramDTO.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(400, "查询条件不能为空！");
        }
        return QueryBuilders.matchQuery(SEARCH_FIELD, key);
    }

    private Goods buildGoods(SpuDTO spu) {
        // 1.商品标题，可以拼接：商品标题、品牌、分类、规格参数等信息
        String categoryName = spu.getCategoryName();
        StringBuilder title = new StringBuilder(spu.getTitle()).append(spu.getBrandName()).append(categoryName);

        // 2.自动补全字段，包含：商品名称、品牌名称、分类名称
        Set<String> suggestion = new HashSet<>();
        suggestion.add(spu.getName());
        suggestion.add(spu.getBrandName());
        String[] names = StringUtils.split(categoryName, "/");
        Collections.addAll(suggestion, names);

        // 3.sku相关数据
        // 3.1.查询出spu下的所有的sku
        List<SkuDTO> skus = spu.getSkus();
        Long spuId = spu.getId();
        if (skus == null) {
            skus = itemClient.querySkuBySpuId(spuId);
        }
        // 3.2.记录销量之和
        long sold = 0;
        // 3.3.记录价格集合
        Set<Long> prices = new TreeSet<>();

        for (SkuDTO sku : skus) {
            // 计算销量
            sold += sku.getSold();
            // 价格
            prices.add(sku.getPrice());
        }
        // 3.4.图片
        String image = StringUtils.substringBefore(skus.get(0).getImages(), ",");

        // 4.规格参数键值对 [{name:"CPU频率", value:"2.5GHz"},{name:"CPU型号", value:"骁龙875"}]
        // 4.1.查询规格参数键值对
        List<SpecParamDTO> params = itemClient.querySpecsValues(spuId, true);
        // 4.2.封装
        List<Map<String,Object>> specs = new ArrayList<>(params.size());
        for (SpecParamDTO param : params) {
            // 创建map
            Map<String,Object> map = new HashMap<>();
            map.put("name", param.getName());
            map.put("value", chooseSegment(param));
            specs.add(map);
        }
        // 创建Goods
        Goods goods = new Goods();
        goods.setUpdateTime(new Date());
        goods.setTitle(title.toString());
        goods.setSuggestion(suggestion);
        goods.setSpecs(specs);
        goods.setSold(sold);
        goods.setPrices(prices);
        goods.setImage(image);
        goods.setId(spuId);
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        return goods;
    }

    private Object chooseSegment(SpecParamDTO p) {
        Object value = p.getValue();
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        if(!p.getNumeric() || StringUtils.isBlank(p.getSegments()) || value instanceof Collection){
            return value;
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }
}
